package gu.sql2java;

import static com.google.common.base.Preconditions.checkNotNull;
import static gu.sql2java.SimpleLog.log;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import com.google.common.base.Function;

import gu.sql2java.exception.QueueStopException;
import gu.sql2java.exception.QueueTimeoutException;

/**
 * 实现向阻塞队列输出数据库记录的{@link TableManager.Action}接口<br>
 * 为异步输出数据库查询记录提供技术基础，
 * 作为数据库记录的生产者向将从数据库中读取的数据库记录转换为指定的类型添加到阻塞队列，供另一端的消费者使用，
 * 当消费端停止消费时，即停止工作。
 * @author guyadong
 *
 * @param <B> 从数据库读取的原始记录对象类型
 * @param <T> 队列中保存的基于B转换的数据库对象类型
 * @since 3.15.4
 */
public class QueueBufferProducerAction<B extends BaseBean,T> implements TableManager.Action<B> {
	/**
	 * 默认队列容量
	 */
	static final int DEFAULT_QUEUE_CAPACITY = 1000;
	/**
	 * 默认队列插入超时时间(秒)
	 */
	static final int DEFAULT_QUEUE_TIMEOUT = 10;
	static final int DEFAULT_BUFFER_CAPACITY = 100;
	/**
	 * 保存数据库记录的阻塞队列
	 */
	private final BlockingQueue<List<T>> queue;
	/**
	 * 数据类型转换器
	 */
	private final Function<B, T>transformer;
	/**
	 * 插入队列超时时间(秒)
	 */
	private final int queueTimeout;
	/**
	 * 缓冲区容量
	 */
	private final int bufferCapacity;
    /**
     * 当前查询的记录总数
     */
    private final long rowCount;
    /**
     * 停止标志
     */
    private final AtomicBoolean stopped;
    /**
     * 是否输出进度
     */
    private final boolean outputProgress;
    /**
     * 读取数据库记录计数器【全局】
     */
    private final AtomicLong totalFetchCount;
    /**
     * 读取记录计时变量
     */
    private final AtomicLong startTimeMills;
    /**
     * 读取数据库记录计数器【当前】
     */
    private volatile long count = 0;
    private List<T> buffer;
    /**
	 * 构造方法<br>
	 * @param queue 阻塞队列
     * @param bufferCapacity 缓冲区容量
     * @param queueTimeout 插入队列超时时间(秒),
	 * 										队列满后超过这个时间不能插入新记录即视消费端中止,并中止插入
	 * 										小于等于0使用默认值{@link #DEFAULT_QUEUE_TIMEOUT}
     * @param stopped 停止标志,为{@code null}忽略,为{@code true}时不再向队列添加数据，抛出{@link QueueTimeoutException}，结束循环
     * @param transformer B到T的数据类型转换器
     * @param totalFetchCount 读取数据库记录计数器,为{@code null}初始化为0
     * @param startTimeMills 读取记录计时变量,为{@code null}初始化为当前时间
     * @param outputProgress 是否输出进度
     * @param rowCount 当前查询的记录总数
	 */
	private QueueBufferProducerAction(BlockingQueue<List<T>> queue, 
	        int bufferCapacity,
	        int queueTimeout, 
	        AtomicBoolean stopped, 
	        Function<B, T> transformer, 
	        AtomicLong totalFetchCount,AtomicLong startTimeMills,
	        boolean outputProgress, long rowCount) {
		this.queue = checkNotNull(queue,"queue is null");
		this.queueTimeout = queueTimeout > 0 ? queueTimeout : DEFAULT_QUEUE_TIMEOUT;
		this.bufferCapacity = bufferCapacity > 0 ? bufferCapacity : DEFAULT_BUFFER_CAPACITY;
		this.transformer = checkNotNull(transformer,"transformer is null");
		this.totalFetchCount = null == totalFetchCount ? new AtomicLong(0) : totalFetchCount;
		this.startTimeMills = null == startTimeMills ? new AtomicLong(System.currentTimeMillis()) : startTimeMills;
		this.outputProgress = outputProgress;
		this.rowCount = rowCount;
		this.stopped = stopped;
	}
	/**
	 * 构造方法<br>
	 * @param queue 阻塞队列
	 * @param bufferCapacity 缓冲区容量
	 * @param queueTimeout 插入队列超时时间(秒),
	 * 										队列满后超过这个时间不能插入新记录即视消费端中止,并中止插入
	 * 										小于等于0使用默认值{@link #DEFAULT_QUEUE_TIMEOUT}
	 * @param stopped 停止标志,为{@code null}忽略,为{@code true}时不再向队列添加数据，抛出{@link QueueTimeoutException}，结束循环
	 * @param transformer B到T的数据类型转换器
	 * @param outputProgress 是否输出进度
	 * @param rowCount 当前查询的记录总数
	 */
	public QueueBufferProducerAction(BlockingQueue<List<T>> queue, 
	        int bufferCapacity,
	        int queueTimeout, 
	        AtomicBoolean stopped, 
	        Function<B, T> transformer, 
	        boolean outputProgress, long rowCount) {
	    this(queue, bufferCapacity,queueTimeout,stopped,transformer,null,null,outputProgress,rowCount);
	}
	/**
	 * 构造方法<br>
	 * @param queueCapaticy 队列容量,小于等于0使用默认值{@link #DEFAULT_QUEUE_CAPACITY}
	 * @param bufferCapacity 缓冲区容量
	 * @param queueTimeout 插入队列超时时间(秒),
	 * 										队列满后超过这个时间不能插入新记录即视消费端中止,并中止插入
	 * 										小于等于0使用默认值{@link #DEFAULT_QUEUE_TIMEOUT}
	 * @param stopped 停止标志,为{@code null}忽略,为{@code true}时不再向队列添加数据，抛出{@link QueueTimeoutException}，结束循环
	 * @param transformer B到T的数据类型转换器
	 * @param outputProgress 是否输出进度
	 * @param rowCount 当前查询的记录总数
	 */
	public QueueBufferProducerAction(int queueCapaticy, 
	        int bufferCapacity,
	        int queueTimeout, 
	        AtomicBoolean stopped, 
	        Function<B, T> transformer, 
	        boolean outputProgress, long rowCount) {
	    this(new LinkedBlockingQueue<List<T>>(queueCapaticy > 0 ? queueCapaticy : DEFAULT_QUEUE_CAPACITY), 
	            bufferCapacity, 
	            queueTimeout,
	            stopped,
	            transformer,
	            outputProgress, rowCount);
	}
	/**
	 * 构造方法<br>
	 * 从原对象复制构造新对象，除了{@link #rowCount}字段都从原对象复制，用于多线程异步查询所需要的action对象构建
	 * @param action
	 * @param rowCount 当前查询的记录总数
	 */
	public QueueBufferProducerAction(QueueBufferProducerAction<B, T> action, long rowCount){
	    this(action.queue, 
	            action.bufferCapacity, 
	            action.queueTimeout,
	            action.stopped,
	            action.transformer, 
	            action.totalFetchCount,
	            action.startTimeMills,
	            action.outputProgress,
	            rowCount);
	}
	/**
	 * 简化版本构造方法<br>
	 * 队列容量和插入队列超时时间使用默认值
	 * @param stopped 停止标志,为{@code null}忽略,为{@code true}时不再向队列添加数据，抛出{@link QueueTimeoutException}，结束循环
	 * @param transformer B到T的数据类型转换器
	 * @param rowCount 当前查询的记录总数
	 */
	public QueueBufferProducerAction(AtomicBoolean stopped, Function<B, T> transformer, long rowCount){
		this(0, DEFAULT_BUFFER_CAPACITY, 0, stopped, transformer, false, rowCount);
	}
	public BlockingQueue<List<T>> getQueue() {
        return queue;
    }
    /**
	 * @return rowCount
	 */
	public long getRowCount() {
		return rowCount;
	}
	
	public int getBufferCapacity() {
        return bufferCapacity;
    }
    /**
	 * 将数据库中获取的原始数据库对象通过转换器({@link #transformer})转换为指定类型插入到阻塞队列中。
	 * 当阻塞队列满添加元素超时即视为消费端停止，即停止工作，抛出{@link QueueTimeoutException}异常，
	 * 以中止从数据库中读取记录的过程
	 */
	@Override
	public void call(B bean) {
	    if(null != stopped && stopped.get()){
	        throw new QueueStopException("stop flag is true");
	    }
	    long tc = totalFetchCount.incrementAndGet();
	    ++count;
	    if(null == buffer){
	        buffer = new ArrayList<>(bufferCapacity);
	    }
        buffer.add(transformer.apply(bean));
	    if(buffer.size() == bufferCapacity || count == rowCount){
	        try {
	            // buffer满或达到记录总数即插入队列
	            if(!queue.offer(buffer, queueTimeout, TimeUnit.SECONDS)){
	                /** 队列添加元素超时，判断为消费端停止，即停止工作 */
	                throw new QueueTimeoutException("CANNOT insert to queue for " + queueTimeout + " seconds ,stop");
	            }
	        } catch (InterruptedException e) {
	            throw new QueueTimeoutException(e);
	        }
	        buffer = null;
        }
	    if((0 == tc%100000) || (count == rowCount && count == tc)){
	        long sms = startTimeMills.get();
	        long cms = System.currentTimeMillis();
	        if(startTimeMills.compareAndSet(sms, cms)){
	            log(outputProgress || BaseTableManager.isDebug(),"FETCH {} rows,{}s", tc, (float)(cms-sms)/1000);
	        }
	    }
	}
	/**
	 * {@link QueueBufferProducerAction}的简化版本,队列类型为数据库原始记录类型
	 * @author guyadong
	 *
	 * @param <B> 从数据库读取的原始记录对象类型
	 * @since 3.15.4
	 */
	public static class SimpleQueueProducerAction<B extends BaseBean> extends QueueBufferProducerAction<B,B >{
		/**
		 * 构造方法<br>
		 * @param bufferCapacity 缓冲区容量
		 * @param queueTimeout 插入队列超时时间(秒),
		 * 										队列满后超过这个时间不能插入新记录即视消费端中止,并中止插入
		 * 										小于等于0使用默认值{@link #DEFAULT_QUEUE_TIMEOUT}
		 * @param stopped 停止标志,为{@code null}忽略,为{@code true}时不再向队列添加数据，抛出{@link QueueTimeoutException}，结束循环
		 * @param rowCount 当前查询的记录总数
		 * @param queueCapaticy 队列容量,小于等于0使用默认值{@link #DEFAULT_QUEUE_CAPACITY}
		 * @param transformer B到T的数据类型转换器
		 */
		public SimpleQueueProducerAction(BlockingQueue<List<B>> queue, int bufferCapacity, int queueTimeout, AtomicBoolean stopped, long rowCount) {
			super(queue, bufferCapacity, queueTimeout, stopped, b->b, false, rowCount);
		}
		/**
		 * 构造方法<br>
		 * @param queueCapaticy 队列容量,小于等于0使用默认值{@link #DEFAULT_QUEUE_CAPACITY}
		 * @param bufferCapacity 缓冲区容量
		 * @param queueTimeout 插入队列超时时间(秒),
		 * 										队列满后超过这个时间不能插入新记录即视消费端中止,并中止插入
		 * 										小于等于0使用默认值{@link #DEFAULT_QUEUE_TIMEOUT}
		 * @param stopped 停止标志,为{@code null}忽略,为{@code true}时不再向队列添加数据，抛出{@link QueueTimeoutException}，结束循环
		 * @param outputProgress 是否输出进度
		 * @param rowCount 记录总数
		 */
		public SimpleQueueProducerAction(int queueCapaticy, int bufferCapacity, int queueTimeout, AtomicBoolean stopped, boolean outputProgress, long rowCount) {
			super(queueCapaticy, bufferCapacity, queueTimeout,stopped, b->b, outputProgress, rowCount);
		}
		/**
		 * 构造方法<br>
		 * 队列容量和插入队列超时时间使用默认值
		 * @param stopped 停止标志,为{@code null}忽略,为{@code true}时不再向队列添加数据，抛出{@link QueueTimeoutException}，结束循环
		 * @param rowCount 当前查询的记录总数
		 */
		public SimpleQueueProducerAction(AtomicBoolean stopped, long rowCount) {
			super(stopped, b->b, rowCount);
		}
		
		  /**
	     * 构造方法<br>
	     * @see QueueBufferProducerAction#QueueBufferProducerAction(QueueBufferProducerAction, long)
	     */
	    public SimpleQueueProducerAction(SimpleQueueProducerAction<B> action,long rowCount){
	        super(action,rowCount);
	    }
	}
}